_Pragma("once")

#include "pch.hpp"

#include "log.hpp"
#include "libmath.hpp"
#include "event.hpp"
#include "window.hpp"
#include "renderer.hpp"
#include "tool.hpp"
#include "inner_bmpfont.hpp"
#include "stock.hpp"
#include "font.hpp"

namespace sdl_engine {

#define RUN_ENGINE() while (!sdl_engine::engine.ShouldExit()) sdl_engine::engine.MainLoop()

class App {
public:
    virtual ~App() = default;

    virtual void Init() {}
    virtual void Shutdown() {}

    virtual bool OnQuit(const EventQuit&) { return true; }
    virtual void OnMouseButtonDown(const EventMouseButtonDown&) {}
    virtual void OnMouseButtonUp(const EventMouseButtonUp&) {}
    virtual void OnMouseMotion(const EventMouseMotion&) {}
    virtual void OnMouseWheel(const EventMouseWheel&) {}
    virtual void OnKeyDown(const EventKeyDown&) {}
    virtual void OnKeyUp(const EventKeyUp&) {}
    virtual void OnWindowResize(const EventWindowResize&) {}
    virtual void OnFingerDown(const EventTouchFinger&) {}
    virtual void OnFingerUp(const EventTouchFinger&) {}
    virtual void OnFingerMotion(const EventTouchFinger&) {}
    virtual void OnMultiGesture(const EventMultiGesture&) {}
    virtual void Render(Renderer&) {}
    virtual void Update([[ maybe_unused ]] int milliseconds) {}
};

struct AndroidInitInfo {
    friend class Engine;

    enum Orientation {
        LANDSCAPE_LEFT,      // top of device left
        LANDSCAPE_RIGHT,     // top of device right
        PORTRAIT,            // top of device up
        PORTRAIT_UPSIDEDOWN  // top of device down
    };

    AndroidInitInfo& SetApkFileVersion(int main, int patch) {
        mainVersion = main;
        patchVersion = patch;
        return *this;
    }
    inline AndroidInitInfo& SetOrientations(std::initializer_list<Orientation> olist) {
        orientations = olist;
        return *this;
    }
    inline AndroidInitInfo& SeperateMouseAndTouch() {
        seperateMouseAndTouch = true;
        return *this;
    }

private:
    std::optional<int> mainVersion;
    std::optional<int> patchVersion;
    bool seperateMouseAndTouch = false;
    std::set<Orientation> orientations;
};

struct OpenGLInitInfo {
    friend class Engine;

    enum Profile {
        CORE,
        ES,
        COMPATIBILITY,
    };

    OpenGLInitInfo(int majorVersion, int minorVersion, Profile profile)
        : majorVersion(majorVersion), minorVersion(minorVersion), profile(profile)
    {}


private:
    int majorVersion;
    int minorVersion;
    Profile profile;
};

class Engine final {
public:
    friend class Renderer;

    constexpr static int NoFPSLimit = -1;

    Engine() = default;
    ~Engine() = default;

    void Init(int argc, char** argv,
              const Window::CreateInfo&,
              const Renderer::CreateInfo&,
              bool canvaResizable = true,
              u32 initFlag = SDL_INIT_EVERYTHING,
              OpenGLInitInfo* = nullptr,
              AndroidInitInfo* = nullptr);
    void Shutdown();

    App* SetApp(App* app);

    inline App* GetApp() { return app_; }

    inline bool IsCanvaResizable() const { return canva_ != nullptr; }

    inline void ResizeCanva(const Size& size) {
        canva_.reset(new Texture(SDL_PIXELFORMAT_RGBA8888,
                                 SDL_TEXTUREACCESS_TARGET,
                                 size));
    }

    inline Size GetCanvaSize() const {
        if (canva_)
            return canva_->GetSize();
        else
            return window_->GetSize();
    }

    inline void ShowFPS() { showFPS_ = true; }
    inline void HideFPS() { showFPS_ = false; }

    inline Window& GetWindow() { return *window_; }
    inline Renderer& GetRenderer() { return *renderer_; }
    inline InnerBmpFont& GetInnerBmpFont() { return *innerBmpFont_;}

    void EventHandle();

    template <typename T>
    using EventCallback = std::function<bool(const T&)>;

    inline bool ShouldExit() const { return shouldExit_; }
    inline void Exit() { shouldExit_ = true; }

    /* @brief if you enable canva resizable, the position will auto map to canva
     * @relate Engine::GetMousePositionNoCVT() const
     */
    inline Point GetMousePosition() const {
        if (IsCanvaResizable())
            return mapGlobalPoint2Canva(realMousePosition_);
        return GetMousePositionNoCVT();
    }

    /* @brief this position won't map to canva
     * @relate Engine::GetMousePosition() const
     */
    inline Point GetMousePositionNoCVT() const {
        return realMousePosition_;
    }

    using UpdateCallback = std::function<void(int)>;
    using RenderCallback = std::function<void(Renderer&)>;
    using InitCallback = std::function<void(void)>;
    using ShutdownCallback = std::function<void(void)>;

    void MainLoop();

    void Update(int milliseconds);
    void Render();

    inline const Color& GetClearColor() const { return clearColor_; }
    inline void SetClearColor(const Color& color) { clearColor_ = color; }

    inline int GetFPS() const { return fps_; }
    inline void SetFPS(int fps) { fps_ = fps; }

    using FontStock = Stock<Font, std::list>;
    FontStock& GetFontStock() { return fontStock_; }

private:
    bool shouldExit_ = false;
    int tick_ = 0;
    Color clearColor_ = {0, 0, 0, 255};
    int fps_ = 60;
    int realTimeFPS_ = 0;
    bool showFPS_ = false;
    SDL_GLContext glContext_ = nullptr;
    Unique<Texture> canva_ = nullptr;
    Point realMousePosition_;

    App* app_ = nullptr;

    FontStock fontStock_;

    Unique<Window> window_ = nullptr;
    Unique<Renderer> renderer_ = nullptr;
    Unique<InnerBmpFont> innerBmpFont_ = nullptr;

    void renderFPS(int fps);
    Point mapGlobalPoint2Canva(const Point&) const;
};

extern Engine engine;

}
